from PySide6.QtCore import QObject, Signal
from PySide6.QtNetwork import QUdpSocket, QHostAddress
import config
import json
import sinodev
import sinodev.tools.list_ports
import traceback


class UDPListener(QObject):
    signalLogger = Signal(str)
    signalNotBound = Signal()

    def __init__(self, host: str, port: int, hwv: str):
        super().__init__()
        self._host = host
        self._port = port
        self._hwv = hwv

    def on_udpThread_started(self):
        self._sock = QUdpSocket(parent=self)
        if self._sock.bind(QHostAddress(self._host), self._port):
            self._sock.readyRead.connect(self.on_readyRead_emitted)
        else:
            self._sock.close()
            self.signalLogger.emit("E: Failed to bind.")
            self.signalNotBound.emit()

    def on_signalMissionCompleted_emitted(self):
        self._sock.close()

    def on_readyRead_emitted(self):
        while self._sock.hasPendingDatagrams():
            try:
                data, addr, port = self._sock.readDatagram(
                    self._sock.pendingDatagramSize()
                )
                self.signalLogger.emit(f"I: Received from {addr.toString()}:{port}.")
                self.signalLogger.emit(data.toStdString())
                if b"start1" in data.data():
                    mark = json.loads(data.toStdString())["Mark"]
                    okay = json.dumps({"Mark": mark, "Receive": "OK"})
                    self._sock.writeDatagram(okay.encode(), addr, port)
                    self.signalLogger.emit(f"I: Sent to {addr.toString()}:{port}.")
                    self.signalLogger.emit(okay)

                    self.reply = {}
                    self.reply["Mark"] = mark
                    self.reply["FirmwareList"] = []
                    self.physical_coms = {}
                    for i, com in enumerate(
                        sinodev.tools.list_ports.comports(
                            serial_baudrate=config.g_cfg["serial_baudrate"],
                            serial_timeout=config.g_cfg["serial_timeout"],
                            device_type=config.g_cfg["device_type"],
                            device_keywords=config.g_cfg["device_keywords"],
                        ),
                        1,
                    ):
                        self.reply["FirmwareList"].append(
                            {
                                "Com": f"COM{i}",
                                "BaseID": self._hwv,
                            }
                        )
                        self.physical_coms[f"COM{i}"] = com

                    self._sock.writeDatagram(
                        json.dumps(self.reply).encode(), addr, port
                    )
                    self.signalLogger.emit(f"I: Sent to {addr.toString()}:{port}.")
                    self.signalLogger.emit(f"{json.dumps(self.reply)}")

                elif b"FirmwareList" in data.data():  # after start1
                    temp = json.loads(data.toStdString())
                    mark = temp["Mark"]
                    snlist = temp["FirmwareList"]
                    okay = json.dumps({"Mark": mark, "Receive": "OK"})
                    self._sock.writeDatagram(okay.encode(), addr, port)
                    self.signalLogger.emit(f"I: Sent to {addr.toString()}:{port}.")
                    self.signalLogger.emit(okay)

                    for i, com in enumerate(
                        sinodev.tools.list_ports.comports(
                            serial_baudrate=config.g_cfg["serial_baudrate"],
                            serial_timeout=config.g_cfg["serial_timeout"],
                            device_type=config.g_cfg["device_type"],
                            device_keywords=config.g_cfg["device_keywords"],
                        ),
                        1,
                    ):
                        if not self.physical_coms[f"COM{i}"] == com:
                            raise ValueError(f"{com} not found.")

                        sn = snlist[i - 1]["SN"]
                        if len(sn) != config.g_cfg["sn_length"]:
                            raise ValueError("Invalid SN received.")

                        rtk = sinodev.SinoRTK(
                            port=com,
                            baudrate=config.g_cfg["serial_baudrate"],
                            timeout=config.g_cfg["serial_timeout"],
                        )
                        rtk.sn_length = config.g_cfg["sn_length"]
                        rtk.hwv_length = config.g_cfg["hwv_length"]
                        if rtk.fill_id() and rtk.set_sn_hwv(
                            sn=sn,
                            hwv=self._hwv,
                            compatibility=config.g_cfg["device_compatibility"],
                        ):
                            self.reply["FirmwareList"][i - 1].update(
                                {
                                    "EFuseID": rtk.id,
                                    "BaseID": rtk.hwv,
                                    "SN": rtk.sn,
                                }
                            )

                    self.reply["Mark"] = mark
                    self.signalLogger.emit("I: Done.")

                    self._sock.writeDatagram(
                        json.dumps(self.reply).encode(), addr, port
                    )
                    self.signalLogger.emit(f"I: Sent to {addr.toString()}:{port}.")
                    self.signalLogger.emit(json.dumps(self.reply))

                elif b"start3" in data.data():
                    mark = json.loads(data.toStdString())["Mark"]
                    okay = json.dumps({"Mark": mark, "Receive": "OK"})
                    self._sock.writeDatagram(okay.encode(), addr, port)
                    self.signalLogger.emit(f"I: Sent to {addr.toString()}:{port}.")
                    self.signalLogger.emit(okay)

                    self.reply = {}
                    self.reply["Mark"] = mark
                    self.reply["FirmwareList"] = []
                    for i, com in enumerate(
                        sinodev.tools.list_ports.comports(
                            serial_baudrate=config.g_cfg["serial_baudrate"],
                            serial_timeout=config.g_cfg["serial_timeout"],
                            device_type=config.g_cfg["device_type"],
                            device_keywords=config.g_cfg["device_keywords"],
                        ),
                        1,
                    ):
                        rtk = sinodev.SinoRTK(
                            port=com,
                            baudrate=config.g_cfg["serial_baudrate"],
                            timeout=config.g_cfg["serial_timeout"],
                        )
                        rtk.hwv_length = config.g_cfg["hwv_length"]
                        rtk.sn_length = config.g_cfg["sn_length"]
                        if rtk.fill_id() and rtk.fill_sn_hwv():
                            self.reply["FirmwareList"].append(
                                {
                                    "Com": f"COM{i}",
                                    "EFuseID": rtk.id,
                                    "SN": rtk.sn,
                                    "BaseID": rtk.hwv,
                                }
                            )

                    self._sock.writeDatagram(
                        json.dumps(self.reply).encode(), addr, port
                    )
                    self.signalLogger.emit(f"I: Sent to {addr.toString()}:{port}.")
                    self.signalLogger.emit(f"{json.dumps(self.reply)}")

            except WindowsError:
                self.signalLogger.emit("E: Connection is interrupted.")
            except NameError:
                self.signalLogger.emit("E: SN Received too early.")
            except ValueError as ve:
                self.signalLogger.emit(f"E: {ve}")
            except IndexError:
                self.signalLogger.emit("E: Unknown response from serial ports.")
            except Exception as e:
                traceback.print_exc()
                self.signalLogger.emit(f"E: Unknown exception. {e}.")
